#include "util_sharedmemory_SharedMemory.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#include "rwlock.h"

//#define DEBUG


/******* MACROS (taken from http://svn.apache.org/repos/asf/tomcat/native/trunk/native/include/tcn.h) *************/

#define LLT(X) ((long)(X))
#define P2J(P)          ((jlong)LLT(P))
#define J2P(P, T)       ((T)LLT((jlong)P))


/******* Shared memory segment functions *****************/

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    createSmSeg
 * Signature: (Ljava/lang/String;I)I
 */
JNIEXPORT jint JNICALL Java_util_sharedmemory_SharedMemory_createSmSeg
  (JNIEnv *jEnv, jobject jObj, jstring location, jint size)
{
	int shmfd;

	//Get the native string from javaString
	const char *cLocation = jEnv->GetStringUTFChars(location, 0);

#ifdef DEBUG
	printf("Create: Global shared segment name is: %s\n", cLocation);
#endif /* DEBUG */

	shmfd = shm_open(cLocation, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU | S_IRWXG);
	if (shmfd < 0)
	{
		perror("shm_open");
#ifdef DEBUG
		printf("Error on shm_open %d, could not create %s\n", errno, cLocation);
#endif /* DEBUG */
		return -1;
	}

	if (ftruncate(shmfd, size) < 0)
	{
		perror("ftruncate");
		return -1;
	}

	jEnv->ReleaseStringUTFChars(location, cLocation);
	return shmfd;
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    openSmSeg
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_util_sharedmemory_SharedMemory_openSmSeg
  (JNIEnv *jEnv, jobject jObj, jstring location)
{
	//Get the native string from javaString
	const char *cLocation = jEnv->GetStringUTFChars(location, 0);

#ifdef DEBUG
	printf("Open: Global shared segment name is: %s\n", cLocation);
#endif /* DEBUG */

	int shmfd = shm_open(cLocation, O_RDWR, S_IRWXU | S_IRWXG);
	if (shmfd < 0)
	{
		perror("shm_open");
		return -1;
	}

	jEnv->ReleaseStringUTFChars(location, cLocation);
	return shmfd;
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    closeSmSeg
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_closeSmSeg
  (JNIEnv *jEnv, jobject jObj, jint shmfd)
{
#ifdef DEBUG
	printf("Close: shared segment id: %d\n", shmfd);
#endif /* DEBUG */
	close(shmfd);
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    removeSmSeg
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_removeSmSeg
  (JNIEnv *jEnv, jobject jObj, jstring location)
{
	//Get the native string from javaString
	const char *cLocation = jEnv->GetStringUTFChars(location, 0);
#ifdef DEBUG
	printf("Remove: Global shared segment name is: %s\n", cLocation);
#endif /* DEBUG */
	if (shm_unlink(cLocation) != 0)
	{
		perror("shm_unlink");
	}
	jEnv->ReleaseStringUTFChars(location, cLocation);
}

/******* mmap functions *****************/

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    mmap
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_util_sharedmemory_SharedMemory_mmap
  (JNIEnv *jEnv, jobject jObj, jint shmfd, jint size)
{
	void *ptr = mmap(NULL, sizeof(char) * size, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0);
	if (ptr == NULL)
	{
		perror("mmap");
		return -1;
	}
	return P2J(ptr);
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    munmap
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_munmap
  (JNIEnv *jEnv, jobject jObj, jlong mmapAddr, jint size)
{
	if (munmap(J2P(mmapAddr, char*), size) != 0)
	{
		perror("munmap");
	}
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    mmapRead
 * Signature: (J[BIII)I
 */
JNIEXPORT jint JNICALL Java_util_sharedmemory_SharedMemory_mmapRead
  (JNIEnv *jEnv, jobject jObj, jlong mmapAddr, jbyteArray buf, jint bufOffset, jint length, jint segmentOffset)
{
	char *shm_addr = J2P(mmapAddr, char*);

#if 0
	jboolean isCopy;
	signed char *bufarr = (signed char *)(jEnv->GetByteArrayElements(buf, &isCopy));
#endif /* 0 */
#if 0 /* 2 */
	jbyte arr[length];
	jbyte *bufarr = arr;
#endif /* 0 - 2 */

	// Copy length bytes from the mmaped memory segment to the given buffer
#if 0
	memcpy(bufarr + bufOffset, shm_addr + segmentOffset, length);
#endif /* 0 */
#if 0 /* 2 */
	memcpy(bufarr, shm_addr + segmentOffset, length);
	jEnv->SetByteArrayRegion(buf, bufOffset, length, bufarr);
#endif /* 0 - 2 */
	jEnv->SetByteArrayRegion(buf, bufOffset, length, (jbyte *)(shm_addr + segmentOffset));
#ifdef DEBUG
	printf("Read: Copying %d bytes from shared-mem+off (%p) to given buffer+off (%p)\n", length, bufarr + bufOffset, shm_addr + segmentOffset);
	//printf("Read: first byte should be (%c)\n", *(shm_addr + segmentOffset));
	//printf("Read: first byte read is (%c)\n", *(bufarr + bufOffset));
#endif /* DEBUG */
#if 0
	if (isCopy)
	{
#ifdef DEBUG
		printf("Read: Calling ReleaseByteArrayElements\n");
#endif /* DEBUG */
		// Copy the elements back
		jEnv->ReleaseByteArrayElements(buf, bufarr, 0);
	}
#endif /* 0 */
	return length;
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    mmapWrite
 * Signature: (J[BIII)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_mmapWrite
  (JNIEnv *jEnv, jobject jObj, jlong mmapAddr, jbyteArray buf, jint bufOffset, jint length, jint segmentOffset)
{
	char *shm_addr = J2P(mmapAddr, char*);

#if 0
	jboolean isCopy;
	signed char *bufarr = (signed char *)(jEnv->GetByteArrayElements(buf, &isCopy));
#endif /* 0 */

	jbyte arr[length];
	jbyte *bufarr = arr;
	jEnv->GetByteArrayRegion(buf, bufOffset, length, bufarr);

	// Copy length bytes from the buffer to the mmaped memory segment
#if 0
	memcpy(shm_addr + segmentOffset, bufarr + bufOffset, length);
#endif /* 0 */
	memcpy(shm_addr + segmentOffset, bufarr, length);
#ifdef DEBUG
	printf("Write: Copying %d bytes from buffer+off (%p) to given shared_mem+off (%p)\n", length, bufarr + bufOffset, shm_addr + segmentOffset);
	//printf("Write: first byte should be (%c)\n", *(bufarr + bufOffset));
	//printf("Write: first byte written is (%c)\n", *(shm_addr + segmentOffset));
#endif /* DEBUG */
#if 0
	if (isCopy)
	{
#ifdef DEBUG
		printf("Write: Calling ReleaseByteArrayElements\n");
#endif /* DEBUG */
		// No need to copy the elements back
		jEnv->ReleaseByteArrayElements(buf, bufarr, JNI_ABORT);
	}
#endif /* 0 */
}





/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    rwlockOpen
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_util_sharedmemory_SharedMemory_rwlockOpen
  (JNIEnv *jEnv, jobject jObj, jstring name)
{
	//Get the native string from javaString
	const char *cName = jEnv->GetStringUTFChars(name, 0);
#ifdef DEBUG
	printf("RWlockOpen: Global name is: %s\n", cName);
#endif /* DEBUG */
	rwlock_t *rwlock = rwlock_create(cName);
	jEnv->ReleaseStringUTFChars(name, cName);
	return P2J(rwlock);
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    rwlockClose
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_rwlockClose
  (JNIEnv *jEnv, jobject jObj, jlong rwAddr)
{
	rwlock_t *rwlock = J2P(rwAddr, rwlock_t*);
	rwlock_close(rwlock);
#ifdef DEBUG
	printf("RWlockClose\n");
#endif /* DEBUG */
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    rwlockDestroy
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_rwlockDestroy
  (JNIEnv *jEnv, jobject jObj, jstring name)
{
	//Get the native string from javaString
	const char *cName = jEnv->GetStringUTFChars(name, 0);
#ifdef DEBUG
	printf("RWlockDestroy: Global name is: %s\n", cName);
#endif /* DEBUG */
	rwlock_destroy(cName);
	jEnv->ReleaseStringUTFChars(name, cName);
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    rwlockAcquireRead
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_rwlockAcquireRead
  (JNIEnv *jEnv, jobject jObj, jlong rwAddr)
{
	rwlock_t *rwlock = J2P(rwAddr, rwlock_t*);
#ifdef DEBUG
	printf("RWlock-ReadLock\n");
#endif /* DEBUG */
	rwlock_read_lock(rwlock);
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    rwlockReleaseRead
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_rwlockReleaseRead
  (JNIEnv *jEnv, jobject jObj, jlong rwAddr)
{
	rwlock_t *rwlock = J2P(rwAddr, rwlock_t*);
#ifdef DEBUG
	printf("RWlock-ReadUnlock\n");
#endif /* DEBUG */
	rwlock_read_unlock(rwlock);
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    rwlockAcquireWrite
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_rwlockAcquireWrite
  (JNIEnv *jEnv, jobject jObj, jlong rwAddr)
{
	rwlock_t *rwlock = J2P(rwAddr, rwlock_t*);
#ifdef DEBUG
	printf("RWlock-WriteLock\n");
#endif /* DEBUG */
	rwlock_write_lock(rwlock);
}

/*
 * Class:     util_sharedmemory_SharedMemory
 * Method:    rwlockReleaseWrite
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_util_sharedmemory_SharedMemory_rwlockReleaseWrite
  (JNIEnv *jEnv, jobject jObj, jlong rwAddr)
{
	rwlock_t *rwlock = J2P(rwAddr, rwlock_t*);
#ifdef DEBUG
	printf("RWlock-WriteUnlock\n");
#endif /* DEBUG */
	rwlock_write_unlock(rwlock);
}

